package com.ethank.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import com.aliyun.ocr_api20210707.Client;
import com.aliyun.ocr_api20210707.models.VerifyVATInvoiceRequest;
import com.aliyun.ocr_api20210707.models.VerifyVATInvoiceResponse;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;
import com.ethank.common.core.domain.AjaxResult;
import com.ethank.common.exception.ServiceException;
import com.ethank.common.utils.StringUtils;
import com.ethank.common.utils.file.FileUploadUtils;
import com.ethank.common.utils.uuid.UUID;
import com.ethank.entity.VoucherResponseVO;
import com.ethank.entity.VoucherResponseVOS;
import com.ethank.entity.log.FnaInvoiceLog;
import com.ethank.entity.oaBill.Fnainvoiceledger;
import com.ethank.enums.AliBillFieldEnum;
import com.ethank.enums.BillEnum;
import com.ethank.mapper.FnaInvoiceLedgerMapper;
import com.ethank.mapper.FnaInvoiceLogMapper;
import com.ethank.mapper.SysUserMapper;
import com.ethank.service.DocService.*;
import com.ethank.service.OcrService;
import com.ethank.utils.BillUtil;
import com.ethank.utils.DataFormatUtil;
import com.ethank.utils.DateCovertUtil;
import org.apache.axis.encoding.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.util.StopWatch;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.xml.bind.JAXBElement;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * @author Administrator
 */
@Service
public class OcrServiceImpl implements OcrService {

    private static final Logger logger = LoggerFactory.getLogger(OcrServiceImpl.class);
    @Resource
    SysUserMapper sysUserMapper;

    @Resource
    private FnaInvoiceLedgerMapper fnaInvoiceLedgerMapper;

    @Resource
    FnaInvoiceLogMapper fnaInvoiceLogMapper;

    @Autowired
    WxServiceImpl wxService;

    @Value("${ocr.accessKeyId}")
    private String accessKeyId;

    @Value("${ocr.accessKeySecret}")
    private String accessKeySecret;

    @Resource
    StringRedisTemplate stringRedisTemplate;
    /**
     * 缓存发票图片KEY
     */
    public final String INVOICE_IMAGE_USER_KEY = "OCR::IMAGE::USER";

    /**
     * 发票数据获取
     *
     * @param files 文件
     * @return 返回
     */
    @Override

    public AjaxResult billUpload(MultipartFile[] files, Integer type, Integer userId) throws Exception {
        if (null == files || files.length == 0) {
            return AjaxResult.error("未检测到文件");
        }

        Long startTime = System.currentTimeMillis();
        BillUtil billUtil = new BillUtil();
        // 查询识别类型对应的方法
        String apiInvoke = BillEnum.getInvokeName(type);
        Method declaredMethod =
            billUtil.getClass().getDeclaredMethod(apiInvoke, InputStream.class, String.class, String.class);
        ArrayList<JSONObject> jsonObjects = new ArrayList<>();
        for (MultipartFile file : files) {
            try {
                InputStream byteArrayInputStream = file.getInputStream();
                // 图片识别
                Object invoke = declaredMethod.invoke(billUtil, byteArrayInputStream, accessKeyId, accessKeySecret);
                JSONObject jsonObject = JSONObject.parse(JSONObject.toJSONString(invoke));
                logger.info("updateTicket=>jsonObject=>" + jsonObject.toString());

                if (!jsonObject.containsKey("code") || !jsonObject.getInteger("code").equals(200)) {
                    logger.error("未获取到识别数据");
                    // 记录到日志
                    // addLogRecord(userId,fileContent,0,null,null);
                    continue;
                }

                // 混贴识别和其它类型识数据响应不同
                if (type.equals(0)) {
                    JSONArray jsonArray = jsonObject.getJSONArray("data");
                    for (int j = 0; j < jsonArray.size(); j++) {
                        JSONObject jsonObj = JSON.parseObject(JSON.toJSONString(jsonArray.get(j)));
                        if (null == BillEnum.getTypeByBillTypeName(jsonObj.getOrDefault("type", "").toString())) {
                            continue;
                        }
                        // 上传文件到服务器
                        String uploadUrl = FileUploadUtils.upload(file);
                        addLogRecord(userId, null, 1, jsonObj.getJSONObject("data"),
                            jsonObj.getOrDefault("type", "").toString(), uploadUrl);

                        List<VoucherResponseVO> voucherResponseVOS = convertToVoucher(jsonObj.getJSONObject("data"));
                        JSONObject jobj = new JSONObject();
                        jobj.put("records", voucherResponseVOS);
                        jobj.put("type", jsonObj.getOrDefault("type", "").toString());
                        jobj.put("mediaId", uploadUrl);
                        jsonObjects.add(jobj);

                    }
                } else {
                    HashMap<String, Object> map = new HashMap<>();
                    map.put("type", BillEnum.getBillTypeName(type));
                    // map.put("mediaId", serverIds[i]);
                    // 将识别数据和图片base64记录到日志数据库
                    map.put("data", jsonObject.getJSONObject("data"));
                    List<VoucherResponseVO> voucherResponseVOS = convertToVoucher(jsonObject.getJSONObject("data"));
                    JSONObject jobj = new JSONObject();
                    jobj.put("records", voucherResponseVOS);
                    jobj.put("type", BillEnum.getBillTypeName(type));
                    jsonObjects.add(jobj);
                    String uploadUrl = FileUploadUtils.upload(file);
                    addLogRecord(userId, null, 1, jobj.getJSONObject("data"), jobj.getOrDefault("type", "").toString(),
                        uploadUrl);
                }
            } catch (IOException e) {
                logger.error("文件上传失败", e);
                return AjaxResult.error("图像内容错误或格式不支持");

            } catch (Exception ex) {
                logger.error("方法调用失败", ex);
                return AjaxResult.error("图像内容错误或格式不支持");
            }
        }
        Long endTime = System.currentTimeMillis();
        logger.info("========> 接口耗时：" + (endTime - startTime));
        if (null == jsonObjects || jsonObjects.size() == 0) {
            return AjaxResult.error("图像内容错误或格式不支持");
        }

        return AjaxResult.success(jsonObjects);
    }

    /**
     * @Description: 核验发票
     * @Param: [invoiceNo, invoiceDate]
     * @return: com.ethank.common.core.domain.AjaxResult
     * @Date: 2024-03-05
     */
    @Override
    public AjaxResult verifyInvoice(String invoiceCode, String invoiceNo, String invoiceDate, String verifyCode) {
        if (StringUtils.isBlank(invoiceNo) || StringUtils.isBlank(invoiceNo)) {
            return AjaxResult.error("请求发票号或开票日期不可为空");
        }
        boolean result = verifyInvoiceData(invoiceCode, invoiceNo, invoiceDate, verifyCode, null);
        return AjaxResult.success(result);

    }

    /**
     * 获取所有票据识别枚举
     *
     * @return 数据
     */
    @Override
    public AjaxResult getBillLists() {
        ArrayList<HashMap<String, Object>> bills = BillEnum.getBills();
        return AjaxResult.success(bills);
    }

    /**
     * @Description: 上传文档到OA服务
     * @Param: [files]
     * @return: com.ethank.common.core.domain.AjaxResult
     * @Author: tf
     * @Date: 2024-03-05
     */

    /**
     * @Description: 保存发票数据到发票夹
     * @Param: [jsonObject]
     * @return: com.ethank.common.core.domain.AjaxResult
     * @Author: tf
     * @Date: 2024-03-05
     */
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public AjaxResult addTicket(JSONObject jsonObject) {
        // 判断是修改还是新增
        if (jsonObject.getString("dataType") != null && "update".equals(jsonObject.getString("dataType"))) {
            // 编辑时判断发票状态
            // 获取发票主表id
            String invoiceId = jsonObject.getOrDefault("id", "").toString();
            if (StringUtils.isNotBlank(invoiceId) && !"0".equals(invoiceId)) {
                Map<String, Object> selectInvoiceMap = fnaInvoiceLedgerMapper.selectInvoiceWriteOff(invoiceId);
                logger.info("OcrServiceImpl.editCheckInvoiceIsUse ==> 发票保存 ==> 发票对象：" + selectInvoiceMap);
                String invoiceStatus = selectInvoiceMap.getOrDefault("status", "999").toString();
                if (!"0".equals(invoiceStatus)) {
                    if ("1".equals(invoiceStatus)) {
                        // 处于流程中
                        // 查询发票流程编号
                        String invoiceNumberStr =
                            selectInvoiceMap.getOrDefault("invoiceNumber", "999999999").toString();
                        logger.info("OcrServiceImpl.deleteInvoiceInfo ==> 发票保存 ==> 要保存的发票号：" + invoiceNumberStr);
                        // 根据发票号查询流程信息
                        Map<String, Object> requestMap =
                            fnaInvoiceLedgerMapper.selectRequestIdByInvoiceNumber(invoiceNumberStr);
                        if (requestMap == null) {
                            return AjaxResult.error("系统错误，请联系管理员...");
                        }
                        String reqId = requestMap.getOrDefault("bxdh", "000000").toString();
                        if ("000000".equals(reqId)) {
                            return AjaxResult.error("删除失败，该发票已被流程占用...");
                        }
                        return AjaxResult.error("保存失败，该发票已被流程占用，占用流程编号：" + requestMap.getOrDefault("bxdh", "000000"));
                    } else if ("2".equals(invoiceStatus)) {
                        // 已核销状态
                        String invoiceNumberStr =
                            selectInvoiceMap.getOrDefault("invoiceNumber", "999999999").toString();
                        logger.info("OcrServiceImpl.deleteInvoiceInfo ==> 发票保存 ==> 要保存的发票号：" + invoiceNumberStr);
                        // 根据发票号查询流程信息
                        Map<String, Object> requestMap =
                            fnaInvoiceLedgerMapper.selectRequestIdByInvoiceNumber(invoiceNumberStr);
                        if (requestMap == null) {
                            return AjaxResult.error("系统错误，请联系管理员...");
                        }
                        return AjaxResult.error("保存失败，该发票已核销，核销流程编号：" + requestMap.getOrDefault("bxdh", "000000"));
                        // return AjaxResult.error("保存失败，该发票已核销...");
                    } else {
                        // 系统错误
                        logger.info("OcrServiceImpl.deleteInvoiceInfo ==> 发票保存 ==> 发票状态错误！！" + invoiceStatus);
                        return AjaxResult.error("系统错误，请联系管理员...");
                    }
                }
            }
        }

        if (!jsonObject.containsKey("userId") || StringUtils.isBlank(jsonObject.getString("userId"))) {
            return AjaxResult.error("用户数据不可为空");
        }
        // 工号转换成员工ID
        // Integer userId = fnaInvoiceLedgerMapper.getUserId(jsonObject.getString("userId"));
        // jsonObject.put("userId",String.valueOf(userId));
        StopWatch stopWatch = new StopWatch("addTicket==>runningStart=>");
        stopWatch.start();
        // 转换相应数据库实体
        String type = jsonObject.get("type").toString();
        Integer billTypeInteger = BillEnum.getTypeByBillTypeName(type);
        if (null == billTypeInteger) {
            return AjaxResult.error("发票类型有误");
        }
        String invoiceNumber = "";
        JSONObject jsonData = jsonObject.getJSONObject("data");
        if (jsonData.containsKey("invoiceNumber") && StringUtils.isBlank(jsonData.getString("invoiceNumber"))) {
            return AjaxResult.error("发票号不可为空");
        } else if (jsonData.containsKey("invoiceNumber")) {
            invoiceNumber = jsonData.getString("invoiceNumber");
        }
        if (jsonData.containsKey("ticketNumber") && StringUtils.isBlank(jsonData.getString("ticketNumber"))) {
            return AjaxResult.error("发票号不可为空");
        } else if (jsonData.containsKey("ticketNumber")) {
            invoiceNumber = jsonData.getString("ticketNumber");
        }

        String price = "";
        if (jsonData.containsKey("totalAmount") && StringUtils.isBlank(jsonData.getString("totalAmount"))) {
            return AjaxResult.error("金额不可为空");
        } else if (jsonData.containsKey("totalAmount")) {
            price = jsonData.getString("totalAmount");
        }
        if (jsonData.containsKey("Amount") && StringUtils.isBlank(jsonData.getString("Amount"))) {
            return AjaxResult.error("金额不可为空");
        } else if (jsonData.containsKey("Amount")) {
            price = jsonData.getString("Amount");
        }
        if (jsonData.containsKey("fare") && StringUtils.isBlank(jsonData.getString("fare"))) {
            return AjaxResult.error("金额不可为空");
        } else if (jsonData.containsKey("fare")) {
            price = jsonData.getString("fare");
        }
        if (StringUtils.isBlank(price)) {
            return AjaxResult.error("金额不可为空");
        }
        // 校验是否是数值型
        if (!StringUtils.isNumeric(price.trim().replace(".", ""))) {
            return AjaxResult.error("金额格式错误");
        }
        // 检验发票是否存在
        if (!jsonData.containsKey("id")
            && (!jsonObject.containsKey("mediaId") || StringUtils.isBlank(jsonObject.getString("mediaId")))) {
            return AjaxResult.error("图片数据不可为空");
        }
        // 保存图片
        if (!jsonData.containsKey("id")) {
            try {
                StopWatch sw = new StopWatch("createDoc=>start");
                sw.start();
                // 2024-07-19 调整文件源
                Integer docId = createNewDoc(jsonObject.getString("mediaId"), jsonObject.getInteger("userId"));
                if (null == docId) {
                    logger.error("上传图片失败");
                }
                sw.stop();
                logger.info("creatDoc=>end=>timeConsuming=>" + sw.getTotalTimeMillis());
                jsonData.put("ocrDocId", docId);
            } catch (Exception e) {
                throw new ServiceException("执行失败,图片上传失败", 0);
            }

        }
        jsonData.put("userId", jsonObject.getString("userId"));
        jsonData.put("type", jsonObject.getString("type"));
        // 0未查验，1有效已查验，2有效未查验，3无效
        Integer checkStatus = 0;
        // 发票核验接口支持包括：增值税专用发票、增值税普通发票（折叠票）、增值税普通发票（卷票）、增值税电子普通发票（含收费公路通行费增值税电子普通发票）、机动车销售统一发票、二手车销售统一发票多种类型发票核验。您可以通过输入发票的关键验证字段，返回真实的票面信息，包括发票类型、发票代码、发票号码、作废标志、开票日期、购方税号及其他发票信息等。当天开具发票当日可查验（T+0）。注意：可能有几小时到十几小时的延迟
        List<String> verifyList = Arrays.asList("增值税专用发票", "卷票", "增值税发票卷票", "增值税发票");
        if (verifyList.contains(jsonObject.getString("type"))
            && new BigDecimal(price).compareTo(new BigDecimal(5000)) > 0) {

            if (StringUtils.isNotBlank(DataFormatUtil.getInvoiceDateOrBlank(jsonData))) {
                Long timestampSeconds = DateCovertUtil.getTimeStamp(DataFormatUtil.getInvoiceDateOrBlank(jsonData));
                String checkCode = jsonData.getString("checkCode");
                String invoiceCode = jsonData.getString("invoiceCode");
                // && StringUtils.isNotBlank(checkCode)
                if (timestampSeconds > 0 && StringUtils.isNotBlank(invoiceCode)
                    && StringUtils.isNotBlank(invoiceNumber)) {

                    String yyyyMMdd = DateCovertUtil.secondsToFormat(timestampSeconds, "yyyyMMdd");

                    // 发票代码、发票号、开票日期、校验码后六位
                    boolean result = verifyInvoiceData(invoiceCode, invoiceNumber, yyyyMMdd,
                        checkCode.substring(Math.max(0, checkCode.length() - 6)), jsonObject);
                    if (result) {
                        checkStatus = 1;
                    } else {
                        checkStatus = 3;
                    }
                }
            }
        }
        jsonData.put("checkStatus", checkStatus);
        // 新增到数据库、新增日志记录
        BillUtil billUtil = new BillUtil();
        billUtil.billType(billTypeInteger, jsonData, "");
        addLogRecord(jsonObject.getInteger("userId"), null, 1, jsonObject.getJSONObject("data"),
            jsonObject.getString("type"), jsonObject.getString("mediaId"));
        stopWatch.stop();
        logger.info("addTick=>end=>timeConsuming=>" + stopWatch.getTotalTimeMillis());
        return AjaxResult.success();
    }

    @Override
    public AjaxResult recognitionTicket(String[] serverIds, Integer type, Integer userId)
        throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
        if (null == serverIds || serverIds.length == 0) {
            return AjaxResult.error("文件数据不可为空");
        }
        StopWatch stopWatch = new StopWatch("recognitionTicket=>start=>");
        stopWatch.start();

        BillUtil billUtil = new BillUtil();
        // 查询识别类型对应的方法
        String apiInvoke = BillEnum.getInvokeName(type);
        Method declaredMethod =
            billUtil.getClass().getDeclaredMethod(apiInvoke, InputStream.class, String.class, String.class);
        ArrayList<JSONObject> jsonObjects = new ArrayList<>();
        // 获取微信服务图片
        for (int i = 0; i < serverIds.length; i++) {
            byte[] fileContent = getImageStream(serverIds[i]);

            if (null == fileContent) {
                logger.error("获取的文件流服务异常");
                continue;
            }
            InputStream byteArrayInputStream = new ByteArrayInputStream(fileContent);
            // 图片识别
            Object invoke = declaredMethod.invoke(billUtil, byteArrayInputStream, accessKeyId, accessKeySecret);
            JSONObject jsonObject = JSONObject.parse(JSONObject.toJSONString(invoke));
            logger.info("updateTicket=>jsonObject=>" + jsonObject.toString());

            if (!jsonObject.containsKey("code") || !jsonObject.getInteger("code").equals(200)) {
                logger.error("未获取到识别数据");
                // 记录到日志
                // addLogRecord(userId,fileContent,0,null,null);
                continue;
            }
            String mediaId = "";
            // 混贴识别和其它类型识数据响应不同
            if (type.equals(0)) {
                JSONArray jsonArray = jsonObject.getJSONArray("data");
                for (int j = 0; j < jsonArray.size(); j++) {
                    JSONObject jsonObj = JSON.parseObject(JSON.toJSONString(jsonArray.get(j)));
                    if (null == BillEnum.getTypeByBillTypeName(jsonObj.getOrDefault("type", "").toString())) {
                        continue;
                    }
                    // 上传图片到服务器
                    if (null != fileContent && fileContent.length > 0
                        && StringUtils.isNotBlank(String.valueOf(fileContent))) {
                        String fileName = UUID.fastUUID().toString(true) + ".png";
                        String contentType = "image/png";
                        try {
                            InputStream fileInputStream = new ByteArrayInputStream(fileContent);
                            MultipartFile file =
                                new MockMultipartFile(fileName, fileName, contentType, fileInputStream);
                            mediaId = FileUploadUtils.upload(file);

                        } catch (Exception e) {
                            logger.error("Error=>addLogRecord=>fileUploadUtil=>" + e.getMessage());
                        }
                    }
                    addLogRecord(userId, null, 1, jsonObj.getJSONObject("data"),
                        jsonObj.getOrDefault("type", "").toString(), mediaId);

                    List<VoucherResponseVO> voucherResponseVOS = convertToVoucher(jsonObj.getJSONObject("data"));
                    JSONObject jobj = new JSONObject();
                    jobj.put("records", voucherResponseVOS);
                    jobj.put("type", jsonObj.getOrDefault("type", "").toString());
                    jobj.put("mediaId", mediaId);
                    jsonObjects.add(jobj);
                }
            } else {
                HashMap<String, Object> map = new HashMap<>();
                map.put("type", BillEnum.getBillTypeName(type));
                map.put("mediaId", serverIds[i]);
                // 将识别数据和图片base64记录到日志数据库
                map.put("data", jsonObject.getJSONObject("data"));
                List<VoucherResponseVO> voucherResponseVOS = convertToVoucher(jsonObject.getJSONObject("data"));
                JSONObject jobj = new JSONObject();
                jobj.put("records", voucherResponseVOS);
                jobj.put("type", BillEnum.getBillTypeName(type));
                // 上传图片到服务器
                if (null != fileContent && fileContent.length > 0
                    && StringUtils.isNotBlank(String.valueOf(fileContent))) {
                    String fileName = UUID.fastUUID().toString(true) + ".png";
                    String contentType = "image/png";
                    try {
                        InputStream fileInputStream = new ByteArrayInputStream(fileContent);
                        MultipartFile file = new MockMultipartFile(fileName, fileName, contentType, fileInputStream);
                        mediaId = FileUploadUtils.upload(file);

                    } catch (Exception e) {
                        logger.error("Error=>addLogRecord=>fileUploadUtil=>" + e.getMessage());
                    }
                }

                jobj.put("mediaId", mediaId);
                jsonObjects.add(jobj);
                addLogRecord(userId, null, 1, jsonObject.getJSONObject("data"), BillEnum.getBillTypeName(type),
                    mediaId);

            }

        }
        stopWatch.stop();
        logger.info("recognitionTicket=>end=>timeConume=>" + stopWatch.getTotalTimeMillis());
        if (null == jsonObjects || jsonObjects.size() == 0) {
            return AjaxResult.error("图像内容错误或格式不支持");
        }

        return AjaxResult.success(jsonObjects);
    }

    private List<VoucherResponseVO> convertToVoucher(JSONObject jsonObj) {
        List<VoucherResponseVO> vos = new ArrayList<>();
        Set<String> keySet = jsonObj.keySet();
        for (String key : keySet) {
            VoucherResponseVO voucherResponseVO = AliBillFieldEnum.convertVoucherVO(key, jsonObj.getString(key));

            try {

                if (key.equals("flights") && StringUtils.isNotBlank(jsonObj.getString("flights"))
                    && jsonObj.getJSONArray("flights").size() > 0) {
                    // 2024-03-22 转换子集
                    List<VoucherResponseVOS> items = new ArrayList<>();
                    jsonObj.getJSONArray("flights").forEach(e -> {
                        List<VoucherResponseVO> responseVos = new ArrayList<>();
                        JSONObject from = JSONObject.from(e);
                        Set<String> strings = from.keySet();
                        VoucherResponseVO childVo = null;
                        for (String chilKey : strings) {
                            childVo = AliBillFieldEnum.convertVoucherVO(chilKey, from.getString(chilKey));
                            responseVos.add(childVo);
                        }
                        VoucherResponseVOS voucherResponseVOS = new VoucherResponseVOS();
                        voucherResponseVOS.setItems(responseVos);
                        items.add(voucherResponseVOS);
                    });
                    if (null == items || items.size() == 0)
                        continue;
                    voucherResponseVO.setChildren(items);
                } else if (key.equals("invoiceDetails") && StringUtils.isNotBlank(jsonObj.getString("invoiceDetails"))
                    && jsonObj.getJSONArray("invoiceDetails").size() > 0) {
                    // 层级转换
                    List<VoucherResponseVOS> items = new ArrayList<>();
                    jsonObj.getJSONArray("invoiceDetails").forEach(e -> {
                        JSONObject from = JSONObject.from(e);
                        Set<String> strings = from.keySet();
                        List<VoucherResponseVO> responseVos = new ArrayList<>();
                        for (String chilKey : strings) {
                            VoucherResponseVO childVo =
                                AliBillFieldEnum.convertVoucherVO(chilKey, from.getString(chilKey));
                            responseVos.add(childVo);
                        }
                        // 费用项、税额、税率都为空则跳过赋值
                        VoucherResponseVO itemName =
                            responseVos.stream().filter(x -> x.getKey().equals("itemName")).findFirst().orElse(null);
                        VoucherResponseVO amount =
                            responseVos.stream().filter(x -> x.getKey().equals("amount")).findFirst().orElse(null);
                        VoucherResponseVO taxRate =
                            responseVos.stream().filter(x -> x.getKey().equals("taxRate")).findFirst().orElse(null);

                        if (null != itemName && null != amount && null != taxRate
                            && StringUtils.isNotBlank(itemName.getValue()) && StringUtils.isNotBlank(amount.getValue())
                            && StringUtils.isNotBlank(taxRate.getValue())) {
                            VoucherResponseVOS voucherResponseVOS = new VoucherResponseVOS();
                            voucherResponseVOS.setItems(responseVos);
                            items.add(voucherResponseVOS);
                        }
                    });

                    if (null == items || items.size() == 0) {
                        continue;
                    }

                    voucherResponseVO.setChildren(items);
                }
            } catch (Exception e) {
                logger.info("详情解析失败");
            }

            if (null != voucherResponseVO) {
                vos.add(voucherResponseVO);
            }

        }
        // 费用明细是否包含住宿和机票文字 如果存在则将备注调整为住宿票和飞机票

        for (int i = 0; i < vos.size(); i++) {
            // 检测发票代码是否为空 为空则将发票号赋值给发票代码
            if (vos.get(i).getKey().equals("invoiceCode") && StringUtils.isBlank(vos.get(i).getValue())) {
                Optional<VoucherResponseVO> optionalInvoiceNumber =
                    vos.stream().filter(v -> v.getKey().equals("invoiceNumber")).findFirst();
                if (optionalInvoiceNumber.isPresent()) {
                    vos.get(i).setValue(optionalInvoiceNumber.get().getValue());
                }

            }
            if (!vos.get(i).getKey().equals("invoiceDetails")) {
                continue;
            }
            VoucherResponseVO voucherResponseVO = vos.get(i);
            if (null == voucherResponseVO.getChildren() || voucherResponseVO.getChildren().size() == 0) {
                break;
            }
            List<VoucherResponseVOS> items = voucherResponseVO.getChildren();
            items.forEach(x -> {
                x.getItems().forEach(item -> {
                    if (StringUtils.isNotBlank(item.getValue()) && item.getValue().contains("住宿")) {

                        Optional<VoucherResponseVO> optionalRemark =
                            vos.stream().filter(v -> v.getKey().equals("remarks")).findFirst();
                        if (optionalRemark.isPresent()) {
                            optionalRemark.get().setValue("住宿票");
                        }

                    } else if (StringUtils.isNotBlank(item.getValue()) && item.getValue().contains("机票")) {
                        Optional<VoucherResponseVO> optionalRemark =
                            vos.stream().filter(v -> v.getKey().equals("remarks")).findFirst();
                        if (optionalRemark.isPresent()) {
                            optionalRemark.get().setValue("飞机票");
                        }
                    } else if (StringUtils.isNotBlank(item.getValue()) && item.getValue().contains("运输服务")) {
                        Optional<VoucherResponseVO> optionalRemark =
                            vos.stream().filter(v -> v.getKey().equals("remarks")).findFirst();
                        if (optionalRemark.isPresent()) {
                            optionalRemark.get().setValue("旅客运输");
                        }
                    }
                });
            });

        }

        return vos;
    }

    /**
     * 获取票据列表及历史 invoiceStartTime、invoiceEndTime、ocrStartTime、ocrEndTime、invoiceType、reimburseStatus 开票日期、识别日期、发票类型、核销状态
     *
     * @return 数据信息
     */
    @Override
    public AjaxResult getInvokeInfo(JSONObject jsonObject) {
        // 如果查询存在mainid，则返回详细信息，如果不存在，则返回发票台账列表
        if (jsonObject.get("mainid") != null && "".equals(jsonObject.get("mainid").toString())) {
            String mainid = jsonObject.get("mainid").toString();
            // 发票类型为 0 1 5 16 信息都在 发票台账表、发票台账扩展表、发票台账明细表内
            // 发票类型为 14 机票，关联两张表，需要单独处理
            // 其余类型通过type获取枚举内的tableName
            String invoiceType = jsonObject.getString("invoiceType");
            if ("0".equals(mainid) || "1".equals(mainid) || "5".equals(mainid) || "16".equals(mainid)) {
                Fnainvoiceledger fnainvoiceledger = fnaInvoiceLedgerMapper.selectList();
            } else if ("14".equals(mainid)) {

            } else {

            }
        } else {
            // 查询发票台账表的数据
            // TODO: 2024-03-11 要新增根据时间和类型筛选
            Fnainvoiceledger fnainvoiceledger = fnaInvoiceLedgerMapper.selectList();
        }

        return null;
    }

    /**
     * @Description: 获取微信服务器相应资源
     * @Param: [serverId]
     * @return: java.io.InputStream
     * @Author: tf
     * @Date: 2024-03-11
     */

    private byte[] getImageStream(String serverId) {

        String accessToken = wxService.getAccessToken();
        if (StringUtils.isBlank(accessToken)) {
            logger.error("updateTicket=>accessToken==>" + accessToken);
            return null;
        }
        InputStream inputStream = getInputStream(accessToken, serverId);
        byte[] data;
        try {
            if (null == inputStream) {
                return null;
            }
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int byteread;
            data = new byte[1024];
            while ((byteread = inputStream.read(data)) != -1) {
                buffer.write(data, 0, byteread);
                buffer.flush();
            }
            data = buffer.toByteArray();
            inputStream.close();
            buffer.close();

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return data;
    }

    // 获取
    public InputStream getInputStream(String accessToken, String mediaId) {
        InputStream is = null;
        String url =
            "https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=" + accessToken + "&media_id=" + mediaId;
        try {
            URL urlGet = new URL(url);
            HttpURLConnection http = (HttpURLConnection)urlGet.openConnection();
            http.setRequestMethod("GET");
            http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            http.setDoOutput(true);
            http.setDoInput(true);
            System.setProperty("sun.net.client.defaultConnectTimeout", "60000");
            System.setProperty("sun.net.client.defaultReadTimeout", "60000");
            http.connect();
            // 获取文件转化为byte流
            is = http.getInputStream();
        } catch (Exception e) {
            logger.error("OcrServiceImpl=>getInputStream=>info=>", e.getMessage());
            return null;
        }
        return is;

    }

    /**
     * @Description: 获取微信服务器相应资源
     * @Param: [serverId]
     * @return: java.io.InputStream
     * @Author: tf
     * @Date: 2024-03-11
     */
    private byte[] getImageStreamByUrl(String url) {

        // String accessToken = wxService.getAccessToken();
        // if (StringUtils.isBlank(accessToken)) {
        // logger.error("updateTicket=>accessToken==>" + accessToken);
        // return null;
        // }
        InputStream inputStream = getInputStreamByUrl(url);
        byte[] data;
        try {
            if (null == inputStream) {
                return null;
            }
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int byteread;
            data = new byte[1024];
            while ((byteread = inputStream.read(data)) != -1) {
                buffer.write(data, 0, byteread);
                buffer.flush();
            }
            data = buffer.toByteArray();
            inputStream.close();
            buffer.close();

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return data;
    }

    // 获取
    public InputStream getInputStreamByUrl(String url) {
        InputStream is = null;
        // String url =
        // "https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=" + accessToken + "&media_id=" + mediaId;
        try {
            URL urlGet = new URL(url);
            HttpURLConnection http = (HttpURLConnection)urlGet.openConnection();
            http.setRequestMethod("GET");
            http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            http.setDoOutput(true);
            http.setDoInput(true);
            System.setProperty("sun.net.client.defaultConnectTimeout", "60000");
            System.setProperty("sun.net.client.defaultReadTimeout", "60000");
            http.connect();
            // 获取文件转化为byte流
            is = http.getInputStream();
        } catch (Exception e) {
            logger.error("OcrServiceImpl=>getInputStream=>info=>", e.getMessage());
            return null;
        }
        return is;

    }

    /**
     * @Description: 上传图片到oa
     * @Param: [mediaId, userId]
     * @return: java.lang.Integer
     * @Author: tf
     * @Date: 2024-03-11
     */

    public Integer createNewDoc(String fileInfo, Integer userId) {
        // fileInfo，如果updateType为1，传微信服务器文件的mediaId
        // fileInfo，如果updateType为2，传文件的url
        DocService docService = new DocService();
        DocServicePortType service = docService.getDocServiceHttpPort();
        // 登陆
        String session = service.login("PDFSC", "Smsh@123..", 0, "123.234.180.90");
        // 根据ID取得文档内容
        DocInfo doc = new DocInfo();
        // 取得该文档的第一个附件
        DocAttachment da = new DocAttachment();
        // TODO: 2024/7/19 判断文件来源，如果是拍照上传，则传mediaId，如果是相册上传，取文件后缀名
        // 1是拍照上传，2是相册上传
        byte[] content = getImageStreamByUrl(fileInfo);
        // if ("1".equals(updateType)) {
        // // 得到附件内容 从微信服务器获取图片相应的数据
        // content = getImageStream(fileInfo);
        // } else if ("2".equals(updateType)) {
        // content = getImageStreamByUrl(fileInfo);
        // }
        // 组装docInfo数据
        da.setDocid(0);
        da.setImagefileid(0);
        ObjectFactory objFac = new ObjectFactory();
        JAXBElement<String> filecontent = objFac.createDocAttachmentFilecontent(Base64.encode(content));
        da.setFilecontent(filecontent);
        JAXBElement<String> docAttachmentFilerealpath = objFac.createDocAttachmentFilerealpath("");
        da.setFilerealpath(docAttachmentFilerealpath);
        da.setIszip(1);

        // if ("1".equals(updateType)) {
        // // 当前的fileInfo是微信服务器的media_id路径
        // JAXBElement<String> fileName = objFac.createDocAttachmentFilename("invoice_"+System.currentTimeMillis()
        // +".png");
        // da.setFilename(fileName);
        // da.setImagefilename(fileName);
        // JAXBElement<String> isextfile = objFac.createDocAttachmentIsextfile("1");
        //
        // da.setIsextfile(isextfile);
        // JAXBElement<String> docFileType = objFac.createDocAttachmentFiletype("2");
        // JAXBElement<String> filetype = objFac.createDocAttachmentFiletype("image/png");
        // da.setDocfiletype(docFileType);
        // da.setFiletype(filetype);
        // } else if ("2".equals(updateType)) {
        // 当前的fileInfo是url路径
        // 获取当前文件后缀
        String fileSuffix = fileInfo.split("\\.")[fileInfo.split("\\.").length - 1].toLowerCase().trim();
        logger.info("文件后缀：" + fileSuffix);
        if ("jpg".equals(fileSuffix) || "png".equals(fileSuffix) || "jpeg".equals(fileSuffix)) {
            JAXBElement<String> fileName =
                objFac.createDocAttachmentFilename("invoice_" + System.currentTimeMillis() + ".png");
            da.setFilename(fileName);
            da.setImagefilename(fileName);
            JAXBElement<String> isextfile = objFac.createDocAttachmentIsextfile("1");

            da.setIsextfile(isextfile);
            JAXBElement<String> docFileType = objFac.createDocAttachmentFiletype("2");
            JAXBElement<String> filetype = objFac.createDocAttachmentFiletype("image/png");
            da.setDocfiletype(docFileType);
            da.setFiletype(filetype);
        } else if ("pdf".equals(fileSuffix)) {
            JAXBElement<String> fileName =
                objFac.createDocAttachmentFilename("invoice_" + System.currentTimeMillis() + "." + fileSuffix);
            da.setFilename(fileName);
            da.setImagefilename(fileName);
            JAXBElement<String> isextfile = objFac.createDocAttachmentIsextfile("1");

            da.setIsextfile(isextfile);
            JAXBElement<String> docFileType = objFac.createDocAttachmentFiletype("2");
            JAXBElement<String> filetype = objFac.createDocAttachmentFiletype("application/pdf");
            da.setDocfiletype(docFileType);
            da.setFiletype(filetype);
        } else if ("ofd".equals(fileSuffix)) {
            JAXBElement<String> fileName =
                objFac.createDocAttachmentFilename("invoice_" + System.currentTimeMillis() + "." + fileSuffix);
            da.setFilename(fileName);
            da.setImagefilename(fileName);
            JAXBElement<String> isextfile = objFac.createDocAttachmentIsextfile("1");

            da.setIsextfile(isextfile);
            JAXBElement<String> docFileType = objFac.createDocAttachmentFiletype("2");
            JAXBElement<String> filetype = objFac.createDocAttachmentFiletype("application/ofd");
            da.setDocfiletype(docFileType);
            da.setFiletype(filetype);
        }

        // }

        // JAXBElement<String> fileName = objFac.createDocAttachmentFilename("invoice_"+System.currentTimeMillis()+"
        // .png");
        // da.setFilename(fileName);
        // da.setImagefilename(fileName);
        // JAXBElement<String> isextfile = objFac.createDocAttachmentIsextfile("1");
        //
        // da.setIsextfile(isextfile);
        // JAXBElement<String> docFileType = objFac.createDocAttachmentFiletype("2");
        // JAXBElement<String> filetype = objFac.createDocAttachmentFiletype("image/png");
        // da.setDocfiletype(docFileType);
        // da.setFiletype(filetype);
        doc.setDoccreaterid(userId);
        doc.setDoccreatertype(0);
        doc.setAccessorycount(1);
        doc.setMaincategory(41);
        doc.setSubcategory(116);
        doc.setSeccategory(1055);
        doc.setOwnerid(userId);
        doc.setDocStatus(9);
        doc.setId(0);
        JAXBElement<String> docSubject = objFac.createDocInfoDocSubject("invoice");
        doc.setDocSubject(docSubject);
        ArrayOfDocAttachment arrayOfDocAttachment = objFac.createArrayOfDocAttachment();
        List<DocAttachment> docAttachment = arrayOfDocAttachment.getDocAttachment();
        docAttachment.add(da);
        doc.setAttachments(objFac.createDocInfoAttachments(arrayOfDocAttachment));
        int docId = service.createDoc(doc, session);
        logger.info("createNewDoc=>docId=>" + docId);
        return docId;
    }

    /**
     * @Description: 新增训用日志
     * @Param: [userId, inputStream]
     * @return: void
     * @Author: tf
     * @Date: 2024-03-12
     */
    public void addLogRecord(Integer userId, byte[] fileContent, Integer status, JSONObject jsonObject, String type,
        String mediaId) {
        FnaInvoiceLog fnaInvoiceLog = new FnaInvoiceLog();
        fnaInvoiceLog.setUserid(userId);
        fnaInvoiceLog.setPosttime(DateCovertUtil.getCurrentTime());
        fnaInvoiceLog.setOcrtype("OCR");
        fnaInvoiceLog.setInterfaceType("票据接口");
        fnaInvoiceLog.setCheckCode(mediaId);
        fnaInvoiceLog.setBase64String(mediaId);
        if (null != fileContent && fileContent.length > 0 && StringUtils.isNotBlank(String.valueOf(fileContent))) {
            // 上传文件
            String fileName = UUID.fastUUID().toString(true) + ".png";
            String contentType = "image/png";
            try {
                InputStream fileInputStream = new ByteArrayInputStream(fileContent);
                MultipartFile file = new MockMultipartFile(fileName, fileName, contentType, fileInputStream);
                mediaId = FileUploadUtils.upload(file);
                fnaInvoiceLog.setBase64String(mediaId);
            } catch (Exception e) {
                logger.error("Error=>addLogRecord=>fileUploadUtil=>" + e.getMessage());
            }

        } else {
            logger.error("Info=>addLogRecord=>setBase64String=>" + mediaId);
            fnaInvoiceLog.setBase64String(mediaId);
        }

        if (null == status || status.equals(0)) {
            // 识别失败
            fnaInvoiceLog.setMsg("发票数据识别失败");
            fnaInvoiceLog.setStatus("失败");
        } else {
            fnaInvoiceLog.setStatus("成功");
            fnaInvoiceLog.setMsg("发票数据识别成功");
        }

        fnaInvoiceLog.setOcrtype(type);
        if (null != jsonObject && jsonObject.size() > 0) {
            fnaInvoiceLog.setInterfaceResult(JSON.toJSONString(jsonObject, JSONWriter.Feature.WriteMapNullValue));
        }
        // 飞机票或火车票没有发票代码和发票号 以ticketNumber为票号
        Integer id = null;
        HashMap<String, Object> filterMap = new HashMap<>();
        filterMap.put("userId", userId);
        if (jsonObject.containsKey("invoiceNumber")) {
            fnaInvoiceLog.setInvoiceNumber(jsonObject.getString("invoiceNumber"));
            fnaInvoiceLog.setInvoiceCode(jsonObject.getString("invoiceCode"));
            filterMap.put("invoiceNumber", jsonObject.getString("invoiceNumber"));
            filterMap.put("invoiceCode", jsonObject.getString("invoiceCode"));
        } else {
            String ticketNumber = jsonObject.getOrDefault("ticketNumber", "").toString();
            fnaInvoiceLog.setInvoiceNumber(ticketNumber);
            String ticketCode = String.valueOf(jsonObject.getOrDefault("ticketCode", ""));
            fnaInvoiceLog.setInvoiceCode(ticketCode);
            filterMap.put("invoiceNumber", ticketNumber);
            filterMap.put("invoiceCode", ticketCode);
        }
        // 检测是否存在 存在则更新
        Integer logId = fnaInvoiceLogMapper.getIdByMap(filterMap);
        if (null == logId) {
            Integer row = fnaInvoiceLogMapper.insertFnaInvoiceLog(fnaInvoiceLog);
            logger.info("addLogRecord=>插入发票日志=>" + row);

        } else {
            // 编辑识别数据 发票号变更后图片资料同步
            if (StringUtils.isNotBlank(mediaId) && null == fileContent) {
                String baseStr = fnaInvoiceLogMapper.getBaseStr(mediaId);
                if (StringUtils.isNotBlank(baseStr)) {
                    fnaInvoiceLog.setBase64String(baseStr);
                }
            }
            // 更新
            fnaInvoiceLog.setId(logId);
            fnaInvoiceLogMapper.updateFnaInvoiceLog(fnaInvoiceLog);
            logger.info("addLogRecord=>更新发票日志=>");
        }

    }

    /**
     * @Description: 查询记录总数
     * @Param: [userId, page, size, beginTime, finishTime, createTime, endTime]
     * @return: com.ethank.common.core.domain.AjaxResult
     * @Author: tf
     * @Date: 2024-03-13
     */
    @Override
    public AjaxResult getListRecord(Integer userId, Integer page, Integer size, String beginTime, String finishTime,
        String createTime, String endTime, String type, Integer status) {
        if (null == userId) {
            return AjaxResult.error("用户信息不可为空");
        }

        HashMap<String, Object> filterMap = new HashMap<>();
        filterMap.put("userId", userId);
        filterMap.put("page", (page - 1) * size + 1);
        filterMap.put("size", page * size);
        if (null != status) {
            filterMap.put("status", status);
        }
        if (StringUtils.isNotBlank(beginTime)) {
            filterMap.put("beginTime", beginTime);
        }
        if (StringUtils.isNotBlank(finishTime)) {
            filterMap.put("finishTime", finishTime);
        }
        if (StringUtils.isNotBlank(createTime)) {
            filterMap.put("createTime", createTime);
        }
        if (StringUtils.isNotBlank(endTime)) {
            filterMap.put("endTime", endTime);
        }
        if (StringUtils.isNotBlank(type)) {
            filterMap.put("type", type);
        }
        // 查询识别数据
        List<Map<String, Object>> invoiceList = fnaInvoiceLedgerMapper.getListRecord(filterMap);
        // 交通类发票备注是空则将上车点 和上车点作为备注数据
        String nullStr = "null";
        for (int i = 0; i < invoiceList.size(); i++) {
            String remark = String.valueOf(invoiceList.get(i).get("remark"));
            String stationGetOn = String.valueOf(invoiceList.get(i).get("stationGetOn"));
            String stationGetOff = String.valueOf(invoiceList.get(i).get("stationGetOff"));

            if ((nullStr.equals(remark) || StringUtils.isBlank(remark)) && StringUtils.isNotBlank(stationGetOn)) {
                stationGetOff = stationGetOff.length() > 0 ? " — " + stationGetOff : "";
                invoiceList.get(i).put("remark", stationGetOn + stationGetOff);
            }
        }
        // 查询总数
        Integer count = fnaInvoiceLedgerMapper.getListRecordCount(filterMap);
        HashMap<String, Object> resultMap = new HashMap<>(3);
        resultMap.put("record", invoiceList);
        Integer totalPages = 0;
        // 计算分页总数
        if (count > 0) {
            totalPages = count / size;
            if (count % size > 0) {
                totalPages = totalPages + 1;
            }
        }
        // 符合当前条件所查询到的所有发票的金额汇总
        BigDecimal totalPrice = fnaInvoiceLedgerMapper.getTotalPrice(filterMap);

        resultMap.put("totalPrice", totalPrice);
        resultMap.put("currentPage", page);
        resultMap.put("totalPages", totalPages);
        return AjaxResult.success(resultMap);
    }

    @Override
    public AjaxResult getInvoiceInfo(String userId, Integer id, String invoiceNumber, String invoiceCode) {
        if (null == userId) {
            return AjaxResult.error("用户ID不可为空");
        }
        if (StringUtils.isBlank(invoiceNumber)) {
            return AjaxResult.error("发票号不可为空");
        }
        HashMap<String, Object> filterMap = new HashMap<>();
        filterMap.put("userId", Integer.valueOf(userId));
        filterMap.put("invoiceNumber", invoiceNumber);
        filterMap.put("invoiceCode", invoiceCode);

        FnaInvoiceLog fnaInvoiceLog = fnaInvoiceLogMapper.getOneByMap(filterMap);
        if (null == fnaInvoiceLog) {
            return AjaxResult.error("信息不存在");
        }
        HashMap<String, Object> resultMap = new HashMap<>(4);
        if (StringUtils.isNotBlank(fnaInvoiceLog.getInterfaceResult())) {
            JSONObject jsonObject = JSONObject.parseObject(fnaInvoiceLog.getInterfaceResult());
            List<VoucherResponseVO> voucherResponseVOS = convertToVoucher(jsonObject);
            resultMap.put("id", id);
            resultMap.put("records", voucherResponseVOS);
        }
        resultMap.put("type", fnaInvoiceLog.getOcrtype());
        resultMap.put("userId", fnaInvoiceLog.getUserid());
        resultMap.put("mediaId", fnaInvoiceLog.getBase64String());
        return AjaxResult.success(resultMap);
    }

    @Override
    public AjaxResult getInvoiceImage(String userId, String invoiceNumber, String invoiceCode) {

        if (null == userId) {
            return AjaxResult.error("用户ID不可为空");
        }
        if (StringUtils.isBlank(invoiceNumber)) {
            return AjaxResult.error("发票号不可为空");
        }
        HashMap<String, Object> resultMap = new HashMap<>(2);
        // 查询缓存
        // String key = INVOICE_IMAGE_USER_KEY + "::" + invoiceNumber + "::" + userId;
        // String redisValue = stringRedisTemplate.opsForValue().get(key);
        // if (StringUtils.isNotBlank(redisValue)) {
        // resultMap.put("base64String", redisValue);
        // return AjaxResult.success(resultMap);
        //
        // }

        HashMap<String, Object> filterMap = new HashMap<>(3);
        filterMap.put("userId", Integer.valueOf(userId));
        filterMap.put("invoiceNumber", invoiceNumber);
        filterMap.put("invoiceCode", invoiceCode);

        FnaInvoiceLog fnaInvoiceLog = fnaInvoiceLogMapper.getOneByMap(filterMap);
        if (null == fnaInvoiceLog) {
            return AjaxResult.error("信息不存在");
        } else if (StringUtils.isBlank(fnaInvoiceLog.getBase64String())) {
            return AjaxResult.error("图片信息不存在");
        }
        resultMap.put("base64String", fnaInvoiceLog.getBase64String());
        // if (StringUtils.isNotBlank(fnaInvoiceLog.getBase64String())) {
        // stringRedisTemplate.opsForValue().set(key, fnaInvoiceLog.getBase64String(), 1, TimeUnit.DAYS);
        // }
        return AjaxResult.success(resultMap);
    }

    public boolean verifyInvoiceData(String invoiceCode, String invoiceNo, String invoiceDate, String verifyCode,
        JSONObject jo) {
        logger.error("OcrServiceImpl.verifyInvoiceData ==> 发票核验 ==> 核验发票：" + JSONObject.toJSONString(jo));
        Config config = new Config().setAccessKeyId(accessKeyId).setAccessKeySecret(accessKeySecret);
        config.endpoint = "ocr-api.cn-hangzhou.aliyuncs.com";
        VerifyVATInvoiceRequest verifyVATInvoiceRequest = null;
        if ("".equals(verifyCode)) {
            verifyVATInvoiceRequest = new VerifyVATInvoiceRequest().setInvoiceNo(invoiceNo).setInvoiceDate(invoiceDate)
                .setInvoiceCode(invoiceCode);
        } else {
            verifyVATInvoiceRequest = new VerifyVATInvoiceRequest().setInvoiceNo(invoiceNo).setInvoiceDate(invoiceDate)
                .setInvoiceCode(invoiceCode).setVerifyCode(verifyCode);
        }

        String taxPrice = null;
        String noTaxPrice = null;
        // 获取不含税价和总价
        try {
            if (jo != null) {
                taxPrice = jo.getOrDefault("fare", "").toString();
                JSONObject data = jo.getJSONObject("data");
                if (data != null) {
                    noTaxPrice = data.getOrDefault("invoiceAmountPreTax", "").toString();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // TODO: 2024/7/9 根据识别类型判断是否传入税额
        if (jo != null) {
            String invoiceTypes = jo.getOrDefault("type", "000").toString();
            if (!invoiceTypes.contains("数电")) {
                // 数电发票大类型区分
                // 增值税专用发票
                if (!invoiceTypes.contains("运输") && (invoiceTypes.contains("专用") || invoiceTypes.contains("增值税"))) {
                    verifyVATInvoiceRequest.setInvoiceSum(noTaxPrice);
                }
            } else if (invoiceTypes.contains("数电") && invoiceTypes.contains("增值税")) {
                verifyVATInvoiceRequest.setInvoiceSum(taxPrice);
            }
        }

        // 设置不含税
        List<String> noTaxPriceList = Arrays.asList("增值税发票");
        if (jo != null && noTaxPriceList.contains(jo.getOrDefault("type", "000").toString())) {
            verifyVATInvoiceRequest.setInvoiceSum(noTaxPrice);
        }

        logger.error("Info=>verifyInvoiceData=>VerifyVATInvoiceRequest=>" + JSON.toJSONString(verifyVATInvoiceRequest));

        RuntimeOptions runtime = new RuntimeOptions();
        try {
            Client client = new Client(config);

            VerifyVATInvoiceResponse verifyVATInvoiceResponse =
                client.verifyVATInvoiceWithOptions(verifyVATInvoiceRequest, runtime);
            JSONObject jsonObject = JSONObject.parseObject(verifyVATInvoiceResponse.getBody().getData());
            logger.error("verifyInvoiceData=>VerifyVATInvoiceResponse=>" + jsonObject.toString());

            // HashMap<String, Object> resultMap = new HashMap<>(2);
            // 请求响应code=001成功,其余code视为核验失败
            String code = jsonObject.getString("code");
            // 返回响应 0-失败 1-成功
            // resultMap.put("checkResult", 0);
            if (StringUtils.isNotBlank(code) && code.equals("001")) {
                // resultMap.put("checkResult", 1);
                return true;
            }
            // String msg = jsonObject.getString("msg");
            // resultMap.put("msg", msg);

        } catch (Exception _error) {
            logger.error(_error.getMessage());
        }
        return false;
    }

    @Override
    public AjaxResult getInvoiceHeader(Integer page, Integer size, String searchKey) {
        HashMap<String, Object> filterMap = new HashMap<>();

        filterMap.put("page", (page - 1) * size + 1);
        filterMap.put("size", page * size);
        if (StringUtils.isNotBlank(searchKey)) {
            filterMap.put("searchKey", searchKey);
        }
        List<Map<String, Object>> maps = fnaInvoiceLedgerMapper.getInvoiceHeaderList(filterMap);
        // 查询总数
        Integer count = fnaInvoiceLedgerMapper.getInvoiceHeaderListCount(filterMap);
        HashMap<String, Object> resultMap = new HashMap<>(3);
        resultMap.put("record", maps);
        Integer totalPages = 0;
        // 计算分页总数
        if (count > 0) {
            totalPages = count / size;
            if (count % size > 0) {
                totalPages = totalPages + 1;
            }
        }
        resultMap.put("currentPage", page);
        resultMap.put("totalPages", totalPages);
        return AjaxResult.success(resultMap);
    }

    /**
     * 删除发票
     *
     * @param map 发票id
     * @return 返回
     */
    @Override
    public AjaxResult deleteInvoiceInfo(Map<String, Object> map) {
        try {
            String id = map.getOrDefault("id", "").toString();
            String delBy = map.getOrDefault("delBy", "").toString();
            if ("".equals(id) || "".equals(delBy)) {
                return AjaxResult.error("请求参数有误");
            }
            // 获取当前时间
            LocalDateTime currentTime = LocalDateTime.now();
            // 定义时间格式
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            // 格式化时间
            String formattedTime = currentTime.format(formatter);
            logger.info("OcrServiceImpl.deleteInvoiceInfo ==> 删除人：" + delBy + " ==> 删除发票id：" + id + " ==> 删除时间："
                + formattedTime);
            // 1.根据发票id判断是否已经触发流程
            // 2.判断该流程是否已核销，不能删除已核销的发票
            // Integer isExists = fnaInvoiceLedgerMapper.selectInvoiceIsExists(id);
            // 查询发票状态是否已核销
            Map<String, Object> selectInvoiceMap = fnaInvoiceLedgerMapper.selectInvoiceWriteOff(id);
            if (null == selectInvoiceMap) {
                return AjaxResult.error("该数据不存在");
            }
            logger.info("OcrServiceImpl.deleteInvoiceInfo ==> 发票删除 ==> 发票对象：" + selectInvoiceMap);
            String invoiceStatus = selectInvoiceMap.getOrDefault("status", "999").toString();
            if (!"0".equals(invoiceStatus)) {
                if ("1".equals(invoiceStatus)) {
                    // 处于流程中
                    // 查询发票流程编号
                    String invoiceNumberStr = selectInvoiceMap.getOrDefault("invoiceNumber", "999999999").toString();
                    logger.info("OcrServiceImpl.deleteInvoiceInfo ==> 发票删除 ==> 要删除的发票号：" + invoiceNumberStr);
                    // 根据发票号查询流程信息
                    Map<String, Object> requestMap =
                        fnaInvoiceLedgerMapper.selectRequestIdByInvoiceNumber(invoiceNumberStr);
                    if (requestMap == null) {
                        return AjaxResult.error("系统错误，请联系管理员...");
                    }
                    String reqId = requestMap.getOrDefault("bxdh", "000000").toString();
                    if ("000000".equals(reqId)) {
                        return AjaxResult.error("删除失败，该发票已被流程占用...");
                    }
                    return AjaxResult.error("删除失败，该发票已被流程占用，占用流程编号：" + requestMap.getOrDefault("bxdh", "000000"));
                } else if ("2".equals(invoiceStatus)) {
                    // 已核销状态
                    String invoiceNumberStr = selectInvoiceMap.getOrDefault("invoiceNumber", "999999999").toString();
                    logger.info("OcrServiceImpl.deleteInvoiceInfo ==> 发票删除 ==> 要删除的发票号：" + invoiceNumberStr);
                    // 根据发票号查询流程信息
                    Map<String, Object> requestMap =
                        fnaInvoiceLedgerMapper.selectRequestIdByInvoiceNumber(invoiceNumberStr);
                    if (requestMap == null) {
                        return AjaxResult.error("系统错误，请联系管理员...");
                    }
                    return AjaxResult.error("删除失败，该发票已核销，核销流程编号：" + requestMap.getOrDefault("bxdh", "000000"));
                    // return AjaxResult.error("删除失败，该发票已核销...");
                } else {
                    // 系统错误
                    logger.info("OcrServiceImpl.deleteInvoiceInfo ==> 发票删除 ==> 发票状态错误！！" + invoiceStatus);
                    return AjaxResult.error("系统错误，请联系管理员...");
                }
            }
            logger.info("OcrServiceImpl.deleteInvoiceInfo ==> 发票删除 ==> 发票删除准备......" + id);
            // 3.如果未触发流程，则物理删除发票信息
            Integer delCount = fnaInvoiceLedgerMapper.deleteInvoiceInfo(id);
            logger.info("OcrServiceImpl.deleteInvoiceInfo ==> 删除条目数：" + delCount);
            // 4.添加日志，新增删除发票人
            return AjaxResult.success(delCount);
        } catch (Exception e) {
            logger.info("OcrServiceImpl.deleteInvoiceInfo ==> 删除失败：" + e);
            return AjaxResult.error(e.toString());
        }

    }

    /**
     * 点击编辑按钮时，验证发票是否在流程中或者已核销的状态
     *
     * @param id 发票信息
     * @return 返回
     */
    @Override
    public AjaxResult editCheckInvoiceIsUse(String id) {
        Map<String, Object> selectInvoiceMap = fnaInvoiceLedgerMapper.selectInvoiceWriteOff(id);
        logger.info("OcrServiceImpl.editCheckInvoiceIsUse ==> 发票编辑 ==> 发票对象：" + selectInvoiceMap);
        String invoiceStatus = selectInvoiceMap.getOrDefault("status", "999").toString();
        if (!"0".equals(invoiceStatus)) {
            if ("1".equals(invoiceStatus)) {
                // 处于流程中
                // 查询发票流程编号
                String invoiceNumberStr = selectInvoiceMap.getOrDefault("invoiceNumber", "999999999").toString();
                logger.info("OcrServiceImpl.deleteInvoiceInfo ==> 发票编辑 ==> 要编辑的发票号：" + invoiceNumberStr);
                // 根据发票号查询流程信息
                Map<String, Object> requestMap =
                    fnaInvoiceLedgerMapper.selectRequestIdByInvoiceNumber(invoiceNumberStr);
                if (requestMap == null) {
                    return AjaxResult.error("系统错误，请联系管理员...");
                }
                String reqId = requestMap.getOrDefault("bxdh", "000000").toString();
                if ("000000".equals(reqId)) {
                    return AjaxResult.error("删除失败，该发票已被流程占用...");
                }
                return AjaxResult.error("编辑失败，该发票已被流程占用，占用流程编号：" + requestMap.getOrDefault("bxdh", "000000"));
            } else if ("2".equals(invoiceStatus)) {
                // 已核销状态
                String invoiceNumberStr = selectInvoiceMap.getOrDefault("invoiceNumber", "999999999").toString();
                logger.info("OcrServiceImpl.deleteInvoiceInfo ==> 发票编辑 ==> 要编辑的发票号：" + invoiceNumberStr);
                // 根据发票号查询流程信息
                Map<String, Object> requestMap =
                    fnaInvoiceLedgerMapper.selectRequestIdByInvoiceNumber(invoiceNumberStr);
                if (requestMap == null) {
                    return AjaxResult.error("系统错误，请联系管理员...");
                }
                return AjaxResult.error("编辑失败，该发票已核销，核销流程编号：" + requestMap.getOrDefault("bxdh", "000000"));
                // return AjaxResult.error("编辑失败，该发票已核销...");
            } else {
                // 系统错误
                logger.info("OcrServiceImpl.deleteInvoiceInfo ==> 发票编辑 ==> 发票状态错误！！" + invoiceStatus);
                return AjaxResult.error("系统错误，请联系管理员...");
            }
        }
        return AjaxResult.success();
    }

    @Override
    public AjaxResult getShopInvoiceList(String shopId, String sign) {
        if (StringUtils.isBlank(sign)) {
            return AjaxResult.error("签名不可为空");
        }
        if (StringUtils.isBlank(shopId)) {
            return AjaxResult.error("门店信息不可为空");
        }
        // 校验签名 暂注释
        if (!checkSign(shopId, sign)) {
            return AjaxResult.error("签名校验失败");
        }
        List<Map<String, Object>> resultList = fnaInvoiceLedgerMapper.getShopInvoiceList(shopId);
        return AjaxResult.success(resultList);
    }

    /**
     * @Description: 校验签名
     * @Param: [shopId, sign]
     * @return: boolean
     * @Author: tf
     * @Date: 2024-10-28
     */
    private boolean checkSign(String shopId, String sign) {

        DateTime now = DateUtil.date();
        String nowStr = DateUtil.format(now, "yyyyMMddHHmm");
        boolean flag = false;
        try {
            String token = DigestUtils.md5DigestAsHex((nowStr + shopId).getBytes("UTF-8"));
            logger.info("Info=>checkSign=nowStr={}==sign={}", nowStr + shopId, token);

            if (token.equals(sign)) {
                flag = true;
            }
            if (!flag) {
                // 时间分钟减1
                DateTime subNow = DateUtil.offsetMinute(now, -1);
                String nowSubStr = DateUtil.format(subNow, "yyyyMMddHHmm");
                String newToken = DigestUtils.md5DigestAsHex((nowSubStr + shopId).getBytes());
                logger.info("Info=>checkSign=nowStr={}==sign={}", nowStr + shopId, token);
                if (newToken.equals(sign)) {
                    flag = true;
                }
            }

        } catch (Exception e) {
            logger.error("签名数据校验失败");
        }
        return flag;
    }

    @Override
    public AjaxResult saveShopInvoice(JSONObject jsonObject) {
        String ids = Convert.toStr(jsonObject.getString("ids"), "");
        String shopId = Convert.toStr(jsonObject.getString("shopId"), "");
        String invoiceNumberList = Convert.toStr(jsonObject.getString("invoiceNumber"), "");
        BigDecimal money = Convert.toBigDecimal(jsonObject.getString("money"), BigDecimal.ZERO);
        BigDecimal rate = Convert.toBigDecimal(jsonObject.getString("rate"), BigDecimal.ZERO);
        String mediaIds = Convert.toStr(jsonObject.getString("mediaIds"), "");
        if (StringUtils.isBlank(shopId)) {
            return AjaxResult.error("门店信息不可为空");
        }
        if (StringUtils.isBlank(ids) || StringUtils.isBlank(invoiceNumberList) || money.compareTo(BigDecimal.ZERO) < 0
            || rate.compareTo(BigDecimal.ZERO) < 0) {
            return AjaxResult.error("请求参数有误");
        }
        // 上传资源到OA
        List<String> serverIds = Arrays.asList(mediaIds.split(","));
        if (null == serverIds || serverIds.size() <= 0) {
            return AjaxResult.error("请上传发票资源不可空");
        }
        List<Integer> docIds = new ArrayList<>();
        for (int i = 0; i < serverIds.size(); i++) {

            Integer docId = createNewDoc(serverIds.get(i), Convert.toInt(shopId, 9483));
            docIds.add(docId);

        }
        // dockIds的内容逗号拼接
        String oaDocIds = docIds.stream().map(String::valueOf).collect(Collectors.joining(","));
        HashMap<String, Object> paramMap = new HashMap<>();
        paramMap.put("oaDocIds", oaDocIds);
        paramMap.put("shopId", shopId);
        paramMap.put("invoiceNumberList", invoiceNumberList);
        paramMap.put("money", money);
        paramMap.put("rate", rate);
        paramMap.put("ids", ids);
        try {
            logger.info("INFO=>saveShopInvoice=>{}", JSON.toJSONString(paramMap));
            Integer count = fnaInvoiceLedgerMapper.saveShopInvoice(paramMap);
            logger.info("INFO=>count=>{}", count);
            // if(count > 0){
                return AjaxResult.success();
            // }
            // return AjaxResult.error("操作失败");
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        return AjaxResult.error();
    }

    /**
     * @Description: 检测门店发票是否使用
     * @Param: [jsonObject]
     * @return: com.ethank.common.core.domain.AjaxResult
     * @Author: tf
     * @Date: 2024-10-28
     */
    @Override
    public AjaxResult checkShopInvoice(JSONObject jsonObject) {
        String invoiceNumber = Convert.toStr(jsonObject.getString("invoiceNumber"), "");
        if (StringUtils.isBlank(invoiceNumber)) {
            return AjaxResult.error("发票号信息有误");
        }
        Integer count = fnaInvoiceLedgerMapper.checkShopInvoice(invoiceNumber);
        if (count > 0) {
            return AjaxResult.error(invoiceNumber + "发票号已使用，请勿重复使用");
        }
        return AjaxResult.success("该发票未使用");
    }
}
